#ifndef XG_HTTPREQUEST_CPP
#define XG_HTTPREQUEST_CPP
////////////////////////////////////////////////////////
#include "../HttpServer.h"
#include "../HttpHelper.h"

void HttpRequest::clear()
{
	hdrsz = 0;
	datsz = 0;
	addsz = 0;
	method = eGET;

	path.clear();
	data.clear();
	node.clear();
	param.clear();
	content.free();
	version.clear();
	payload.clear();
	boundary.clear();
}
bool HttpRequest::parseBoundary()
{
	string contype = getHeadValue("Content-Type");

	boundary.clear();

	if (contype.find("multipart/form-data") == string::npos) return true;

	size_t pos = contype.find("boundary");

	if (pos == string::npos) return false;

	const char* str = SkipStartString(contype.c_str() + pos + 8, " \r\t");

	CHECK_FALSE_RETURN(*str == '=');

	str = SkipStartString(str + 1, " \r\t");

	const char* end = strchr(str, ';');

	boundary = (end ? string(str, end) : str);

	return true;
}
bool HttpRequest::parseHeader(const char* msg, int len)
{
	const char* str = msg;
	const char* end = NULL;
	
	if (len <= 8) return false;

	if (memcmp(str, "GET", 3) == 0)
	{
		method = eGET;
		str += 4;
	}
	else if (memcmp(str, "PUT", 3) == 0)
	{
		method = ePUT;
		str += 4;
	}
	else if (memcmp(str, "POST", 4) == 0)
	{
		method = ePOST;
		str += 5;
	}
	else if (memcmp(str, "HEAD", 4) == 0)
	{
		method = eHEAD;
		str += 5;
	}
	else if (memcmp(str, "DELETE", 6) == 0)
	{
		method = eDELETE;
		str += 7;
	}
	else if (memcmp(str, "OPTIONS", 7) == 0)
	{
		method = eOPTION;
		str += 8;
	}
	else
	{
		return false;
	}

	while (*str && isspace(*str)) str++;

	end = msg + len - 6;
	
	while (str < end)
	{
		if (isspace(*end))
		{	
			version = string(end + 1, msg + len);

			path = ParsePath(string(str, end), param);

			if (param.length() > 0) data.parse(param);

			return true;
		}

		--end;
	}

	return false;
}
bool HttpRequest::isMobile() const
{
	const string& client = stdx::tolower(getHeadValue("User-Agent"));

	if (client.length() < 4) return false;
	
	return strstr(client.c_str(), "android") || strstr(client.c_str(), "iphone") || strstr(client.c_str(), "ipad");
}
string HttpRequest::getHeadString() const
{
	return node.toString();
}
bool HttpRequest::setContentType(const string& val)
{
	return setHeadValue("Content-Type", val);
}
string HttpRequest::getCookie(const string& key) const
{
	string cookie = getHeadValue("Cookie");

	if (cookie.empty()) return cookie;

	cookie = ";" + stdx::replace(cookie, "; ", ";");

	size_t pos = cookie.find(";" + key + "=");

	if (pos == string::npos) return stdx::EmptyString();

	cookie = cookie.substr(pos + key.length() + 2);
	pos = cookie.find(';');

	return stdx::trim(pos == string::npos ? cookie : cookie.substr(0, pos));
}
string HttpRequest::getDataValue(const string& key) const
{
	return data.getValue(key);
}
string HttpRequest::getHeadValue(const string& key) const
{
	string val = node.getValue(key);
	
	return val.empty() ? node.getValue(stdx::tolower(key)) : val;
}
int HttpRequest::getParameterKeys(vector<string>& vec) const
{
	vec = data.getKeys();

	return vec.size();
}
bool HttpRequest::parseHeader(const char* msg)
{
	const char* str = strstr(msg, "\r\n");

	if (str == NULL || str == msg) return false;

	CHECK_FALSE_RETURN(parseHeader(msg, str - msg) && node.parse(str + 2));

	return method == eGET || parseBoundary();
}
void HttpRequest::init(const string& path, E_HTTP_METHOD method, bool simplify)
{
	this->method = method == eUNKNOWN && strchr(path.c_str(), '?') ? eGET : method;
	this->path = ParsePath(path, param);

	if (simplify)
	{
		setHeadValue("Connection", "keep-alive");
	}
	else
	{
		setHeadValue("Connection", "keep-alive");
		setHeadValue("Cache-Control", "no-cache");
		setHeadValue("Accept-Language", "zh-CN,zh;q=0.8");
		setHeadValue("Accept-Encoding", "gzip,deflate,sdch");
		setHeadValue("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");

		string ua = GetUserAgent();

		if (ua.empty()) SetUserAgent(ua = "Chrome/50.0 Mozilla/5.0");

		setHeadValue("User-Agent", ua);
	}
}
int HttpRequest::init(const HttpFrame& frame)
{
	const auto& head = frame.getHead();

	method = frame.getMethod();
	content = frame.getData();
	hdrsz = frame.hdrsz;
	datsz = frame.datsz;
	param = frame.param;
	version = "HTTP/2";
	path = frame.path;
	node = head;

	if (content.size() < hdrsz + datsz) return XG_DATAERR;

	if (method == eGET)
	{
		if (param.length() > 0) data.parse(param, true);
	}
	else
	{
		parseBoundary();

		if (datsz > 0 && boundary.empty())
		{
			payload = string(content.str() + hdrsz, content.str() + hdrsz + datsz);

			data.parse(payload, true);
		}
	}

	return XG_OK;
}
int HttpRequest::init(Socket* sock, SmartBuffer& data, int& readed, bool tryread)
{
	int len = 0;
	char* buffer = data.str();
	const int maxsz = data.size();

	if (hdrsz > 0)
	{
		if ((len = min(addsz, maxsz - readed)) <= 0) return XG_DATAERR;

		if ((len = sock->read(buffer + readed, len, false)) < 0) return len;
	
		if (len == 0) return XG_TIMEOUT;

		readed += len;
	}
	else
	{
		if (tryread)
		{
			if ((len = sock->read(buffer + readed, maxsz - readed, false)) < 0) return len;

			if (len == 0) return XG_TIMEOUT;
		}

		buffer[readed += len] = 0;

		char* end = strstr(buffer, HTTP_REQHDR_ENDSTR);

		if (end == NULL) return readed < maxsz ? XG_TIMEOUT : XG_DATAERR;

		*end = 0;

		if (!parseHeader(buffer)) return XG_DATAERR;

		*end = *HTTP_REQHDR_ENDSTR;
		end += HTTP_REQHDR_ENDSTR_LEN;
		
		string str = getHeadValue("Content-Length");

		if (str.length() > 0 && (datsz = stdx::atoi(str.c_str())) < 0) return XG_DATAERR;

		hdrsz = end - buffer;
	}

	addsz = hdrsz + datsz - readed;

	if (addsz > 0 && readed < maxsz) return XG_TIMEOUT;

	if (datsz > 0 && addsz == 0 && boundary.empty())
	{
		payload = string(buffer + hdrsz, buffer + hdrsz + datsz);

		this->data.parse(payload, false);
	}

	if (addsz < 0)
	{
		content.malloc(hdrsz + datsz);
		memcpy(content.str(), data.str(), hdrsz + datsz);
	}
	else
	{
		data.truncate(readed);
		content = data;
	}

	return readed;
}
string HttpRequest::toString() const
{
	SmartBuffer data = toBuffer();

	return data.isNull() ? stdx::EmptyString() : string(data.str(), data.str() + data.size());
}
SmartBuffer HttpRequest::toBuffer() const
{
	SmartBuffer buffer;
	string msg = getPayload();
	string str = stdx::EncodeURL(SkipStartString(path.c_str(), " \r\n\t/"));

	switch(method)
	{
		case eGET:
			if (param.empty() && data.size() > 0)
			{
				const_cast<HttpRequest*>(this)->param = data.toString();
			}
			str = "GET /" + str;
			break;
		case ePUT:
			str = "PUT /" + str;
			break;
		case ePOST:
			str = "POST /" + str;
			break;
		case eHEAD:
			str = "HEAD /" + str;
			break;
		case eDELETE:
			str = "DELETE /" + str;
			break;
		case eOPTION:
			str = "OPTIONS /" + str;
			break;
		case eUNKNOWN:
			str = msg.empty() && data.empty() ? "GET /" + str : "POST /" + str;
			break;
		default:
			return content;
	}

	if (str[0] == 'P')
	{
		string contype = getHeadValue("Content-Type");

		if (contype.empty())
		{
			const_cast<HttpRequest*>(this)->setContentType("application/x-www-form-urlencoded");
		}
	}

	if (param.empty())
	{
		str += " " + version + node.getEndSpliter();
	}
	else
	{
		str += "?" + param + " " + version + node.getEndSpliter();
	}

	if (hdrsz > 0 && hdrsz + datsz == content.size())
	{
		if (datsz > 0) memcpy(buffer.malloc(datsz), content.str() + hdrsz, datsz);
	}
	else
	{
		if (msg.empty() && data.size() > 0) msg = data.toString();

		const_cast<HttpRequest*>(this)->setHeadValue("Content-Length", stdx::str(msg.length()));
	}

	str += node.toString();
	str += node.getEndSpliter();
	str += node.getEndSpliter();

	if (buffer.size() > 0)
	{
		int len = str.length();
		SmartBuffer content(len + buffer.size());

		memcpy(content.str(), str.c_str(), len);
		memcpy(content.str() + len, buffer.str(), buffer.size());

		return content;
	}

	if (msg.length() > 0) str += msg;

	return buffer = str;
}
string HttpRequest::getDataString() const
{
	if (payload.length() > 0) return payload;

	return data.size() > 0 ? data.toString() : param;
}
bool HttpRequest::setCookie(const string& val)
{
	return setHeadValue("Cookie", val);
}
bool HttpRequest::setParameter(const string& key, const string& val)
{
	return setDataValue(key, val);
}
bool HttpRequest::setDataValue(const string& key, const string& val)
{
	if (method == eGET)
	{
		param.clear();
	}
	else
	{
		payload.clear();
	}

	return data.setValue(key, val);
}
bool HttpRequest::setHeadValue(const string& key, const string& val)
{
	node[key] = val;

	return true;
}
string HttpRequest::ParsePath(const string& path, string& param)
{
	string url = path;
	size_t pos = url.find('?');

	if (pos == string::npos)
	{
		pos = url.find('#');
		param.clear();
	}
	else
	{
		size_t idx = url.find('#');

		if (idx == string::npos)
		{
			param = url.substr(pos + 1);
		}
		else
		{
			param = url.substr(pos + 1, idx);
		}
	}

	if (pos != string::npos) url = url.substr(0, pos);

	url = stdx::trim(url, HTTP_SPACELIST);

	return url.empty() ? "/" : stdx::DecodeURL(url);
}
bool HttpRequest::GetInfoFromURL(const string& url, string& host, string& path, string& ip, int& port, bool& crypted)
{
	const char* end = NULL;
	const char* str = url.c_str();

	crypted = false;
	path.clear();
	host.clear();
	port = 0;

	if (strncmp(str, "http://", 7) == 0 || strncmp(str, "HTTP://", 7) == 0)
	{
		crypted = false;
		str = str + 7;
	}
	else if (strncmp(str, "https://", 8) == 0 || strncmp(str, "HTTPS://", 8) == 0)
	{
		crypted = true;
		str = str + 8;
	}

	end = strchr(str, ':');

	if (end)
	{
		host = string(str, end++);

		if (host.find('?') == string::npos)
		{
			path = SkipStartString(end, "0123456789");
			port = stdx::atoi(end);
		}
		else
		{
			host.clear();
		}
	}

	if (host.empty())
	{
		if ((end = strchr(str, '/')) || (end = strchr(str, '?')))
		{
			host = string(str, end);
			path = end;
		}
		else
		{
			host = str;
		}
	}

	if (port <= 0) port = crypted ? 443 : 80;
	if (path.empty() || path[0] == '?') path = "/" + path;

	ip = Socket::GetHostAddress(host);

	return ip.length() > 0;
}
SmartBuffer HttpRequest::getResult(sp<Socket> sock, bool decode, int timeout) const
{
	sp<HttpResponse> response = getResponse(sock, decode, timeout);

	return response ? response->getResult() : SmartBuffer();
}
sp<HttpResponse> HttpRequest::getResponse(sp<Socket> sock, bool decode, int timeout) const
{
	if (!sock) return NULL;

	SmartBuffer data = toBuffer();

	if (sock->write(data.str(), data.size()) < 0)
	{
		sock->close();

		return NULL;
	}

	sp<HttpResponse> response = newsp<HttpResponse>();

	if (response->init(sock, decode, timeout) < 0)
	{
		sock->close();

		return NULL;
	}

	if (response->isKeepAlive()) return response;

	sock->close();

	return response;
}
SmartBuffer HttpRequest::getResult(const string& host, int port, bool crypted, bool decode, int timeout) const
{
	sp<HttpResponse> response = getResponse(host, port, crypted, decode, timeout);

	return response ? response->getResult() : SmartBuffer();
}
sp<HttpResponse> HttpRequest::getResponse(const string& host, int port, bool crypted, bool decode, int timeout) const
{
	return getResponse(HttpHelper::Connect(host, port, crypted), decode, timeout);
}
////////////////////////////////////////////////////////
#endif